/**
 *  This file is part of PennerBot
 *
 *  Copyright (C) 2010 microkernel
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cmath>

#include <core/anticaptcha.hpp>

inline double round(double d)
{
	return floor(d+0.5);
}

namespace PBot
{
namespace Core
{

void ac_error_exit(j_common_ptr cinfo)
{
  ac_error_ptr myerr = (ac_error_ptr) cinfo->err;
  (*cinfo->err->output_message) (cinfo);
  longjmp(myerr->setjmp_buffer, 1);
}

void ac_output_message(j_common_ptr cinfo)
{
}

AntiCaptcha* AntiCaptcha::instance = NULL;



AntiCaptcha::AntiCaptcha()
 : buffer(NULL), image(NULL), height(0), width(0), xout(-1), yout(-1)
{
    // Calculate vector transformations and save them for better speed
    for(int digit=0; digit < numDigits; digit++)
    {
       std::vector<std::vector<std::pair<unsigned int, unsigned int> > > digittmp;

       for(int deg=0; deg < 11; deg++)
       {
           std::vector<std::pair<unsigned int, unsigned int> > tmp;

           for(int i = 0; i < lenDigits[digit]; ++i)
           {
			   tmp.push_back(std::make_pair(round(digits[digit][i][0] * cos[deg]
                                               - digits[digit][i][1] * sin[deg]),
												  round(digits[digit][i][0] * sin[deg]
                                               + digits[digit][i][1] * cos[deg])));
           }

           digittmp.push_back(tmp);
       }

       transformed.push_back(digittmp);
   }
}

AntiCaptcha::~AntiCaptcha()
{

}

AntiCaptcha& AntiCaptcha::GetInstance()
{
    if(!instance)
    {
        instance = new AntiCaptcha();
    }

    return *instance;
}

void AntiCaptcha::Destroy()
{
    delete instance;
    instance = NULL;
}

AntiCaptcha::Status AntiCaptcha::Crack(const std::string& filename, int& x, int& y)
{
    {
        boost::recursive_mutex::scoped_lock lock(ac_mutex);

        x = -1;
        y = -1;

        if(!LoadFromFile(filename))
        {
            return IOERROR;
        }

        if(!Analyse())
        {
            free(buffer);
            return CRACKFAIL;
        }

        free(buffer);

        buffer = NULL;
    }

    x = xout;
    y = yout;

    //SaveToFile(filename+"_cracked.jpg");

    return CRACKOKAY;
}

bool AntiCaptcha::LoadFromFile(const std::string& filename)
{
    struct jpeg_decompress_struct cinfo;
    struct ac_error_mgr jerr;

    FILE* infile = fopen(filename.c_str(), "rb");

    if(infile == NULL)
    {
        return false;
    }

    cinfo.err = jpeg_std_error(&jerr.pub);
    jerr.pub.error_exit = ac_error_exit;
    jerr.pub.output_message = ac_output_message;

    if (setjmp(jerr.setjmp_buffer))
    {
        jpeg_destroy_decompress(&cinfo);
        fclose(infile);
        return false;
    }

    jpeg_create_decompress(&cinfo);

    jpeg_stdio_src(&cinfo, infile);
    jpeg_read_header(&cinfo, true);

    height = cinfo.image_height;
    width = cinfo.image_width;

    cinfo.out_color_space = JCS_RGB;
    jpeg_start_decompress(&cinfo);

    buffer = static_cast<unsigned char*> (malloc(cinfo.image_width
            * cinfo.image_height * sizeof(unsigned char) * 3));

    int row_stride;
    row_stride = cinfo.output_width * cinfo.output_components;

    JSAMPARRAY tmpBuf;
    tmpBuf = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE,
            row_stride, 1);

    unsigned int p = 0;

    while(cinfo.output_scanline < cinfo.output_height)
    {
        jpeg_read_scanlines(&cinfo, tmpBuf, 1);

        memcpy(&buffer[p], tmpBuf[0], row_stride);
        p += row_stride;
    }

    jpeg_finish_decompress(&cinfo);
    fclose(infile);
    jpeg_destroy_decompress(&cinfo);

    image = reinterpret_cast<ImageBuffer*> (buffer);

    return true;
}

bool AntiCaptcha::Analyse()
{
    // Differentiate between background and foreground
    for(unsigned int y = 0; y < height; ++y)
    {
        for(unsigned int x = 0; x < width; ++x)
        {
            if(image->pixels[y][x][0] >= 160 && image->pixels[y][x][1] >= 160
                    && image->pixels[y][x][2] >= 160)
            {
                image->pixels[y][x][0] = 0xFF;
                image->pixels[y][x][1] = 0xFF;
                image->pixels[y][x][2] = 0xFF;
            }
            else
            {
                image->pixels[y][x][0] = 0x00;
                image->pixels[y][x][1] = 0x00;
                image->pixels[y][x][2] = 0x00;
            }
        }
    }

    // Compare vectors with image
    unsigned int xa, ya;

    bool found = true;
    int nr = -1;
    int deg = 0;

    for(int digit = 0; digit < numDigits; ++digit)
    {
        for(unsigned int y = 0; y < height; ++y)
        {
            for(unsigned int x = 0; x < width; ++x)
            {

                for(deg = 0; deg < 11; ++deg)
                {
                    xa = x;
                    xout = x;
                    ya = y;
                    yout = y;

                    found = true;

                    for(int i = 0; i < lenDigits[digit]; ++i)
                    {
                        xa += transformed[digit][deg][i].first;
                        ya += transformed[digit][deg][i].second;

                        if(xa < 0 || xa >= width || ya < 0 || ya >= height)
                        {
                            found = false;
                            break;
                        }

                        if(!image->pixels[ya][xa][0] == 0x00)
                        {
                            found = false;
                            break;
                        }
                    }

                    if(found)
                    {
                        break;
                    }

                }

                if(found)
                {
                    break;
                }
            }

            if(found)
            {
                break;
            }
        }

        if(found)
        {
            nr = digit;
            break;
        }
    }

    // Mark detected digit
    if(nr != -1)
    {
        xa = xout;
        ya = yout;

        for(int i = 0; i < lenDigits[nr]; ++i)
        {
            xa += transformed[nr][deg][i].first;
            ya += transformed[nr][deg][i].second;

            image->pixels[ya][xa][0] = 0xFF;

        }
    }

    if(nr == -1)
    {
        return false;
    }

    // Select random point of digit
    int vec = random(0,lenDigits[nr]);
    for(int i=0; i < vec; ++i)
    {
        xout += transformed[nr][deg][i].first;
        yout += transformed[nr][deg][i].second;
    }

    return true;
}

/*void AntiCaptcha::SaveToFile(const std::string& filename)
{
    struct jpeg_compress_struct cinfo;
    struct jpeg_error_mgr jerr;
    FILE* outfile;
    JSAMPROW row_pointer[1];
    int row_stride;

    cinfo.err = jpeg_std_error(&jerr);
    jpeg_create_compress(&cinfo);

    if((outfile = fopen(filename.c_str(), "wb")) == NULL)
    {
        return;
    }
    jpeg_stdio_dest(&cinfo, outfile);

    cinfo.image_width = width;
    cinfo.image_height = height;
    cinfo.input_components = 3;
    cinfo.in_color_space = JCS_RGB;

    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, 100, TRUE);
    jpeg_start_compress(&cinfo, TRUE);
    row_stride = 205 * 3;

    while(cinfo.next_scanline < cinfo.image_height)
    {
        row_pointer[0] = &buffer[cinfo.next_scanline * row_stride];
        (void) jpeg_write_scanlines(&cinfo, row_pointer, 1);
    }

    jpeg_finish_compress(&cinfo);
    fclose(outfile);
    jpeg_destroy_compress(&cinfo);
}*/

}
}
